package good.idea.data.excel.write;

import good.idea.data.excel.call.DataLoadChannel;
import good.idea.data.excel.context.ExcelWriteContext;
import good.idea.data.excel.domain.PageData;
import good.idea.data.excel.domain.SheetDataChannel;
import good.idea.data.excel.entity.CellEntity;
import good.idea.data.excel.entity.RowEntity;
import good.idea.data.excel.entity.TemplateTag;
import good.idea.data.excel.listener.DataLoadListener;
import good.idea.data.utils.*;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFDataFormat;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

import java.lang.reflect.Method;
import java.util.*;

/**
 * @author chm
 */
@Data
public class ExcelWriteTask {

    private int tempSheetIndex;

    private int sheetIndex;

    private int writeRowNum;

    private SXSSFWorkbook workbook;

    private Sheet activitySheet;

    private Map<String, Method> methodMap = new HashMap<String, Method>();

    private Map<String, String> onglMap = new HashMap<String, String>();

    private List<RowEntity> rowEntities;

    private ExcelWriteContext writeContext;

    private int batchDataSize = 10000;

    private int lastBindKey;


    private Map<String, DataLoadChannel> dataLoadChannelMap = new HashMap<>();

    private PageData pageData;

    private Map<String, PageData> pageDataHashMap = new HashMap<>();

    public PageData getCurrPageData(String key) {
        PageData pageData = pageDataHashMap.get(key);
        if (pageData == null || "-1".equals(key)) {
            pageData = new PageData();
            pageDataHashMap.put(key, pageData);
        } else {
            pageData.setCurrPage(pageData.getCurrPage() + 1);
        }
        return pageData;
    }

    public DataLoadChannel getDataLoadChannel(String key) {
        Map<Integer, SheetDataChannel> sheetDataChannelMap = writeContext.getSheetDataChannelMap();
        SheetDataChannel sheetDataChannel = sheetDataChannelMap.get(tempSheetIndex);
        Map<String, DataLoadChannel> dataLoadChannelMap = sheetDataChannel.getDataLoadChannelMap();
        if (dataLoadChannelMap.size() == 1) {
            for (Map.Entry<String, DataLoadChannel> entry : dataLoadChannelMap.entrySet()) {
                DataLoadChannel value = entry.getValue();
                dataLoadChannelMap.put(key, value);
                return value;
            }
        }
        return dataLoadChannelMap.get(key);
    }

    public boolean hasNextPageData(String key) {
        if (pageData != null) {
            if (pageData.getCurrPage() < pageData.getPages()) {
                return true;
            }
        }
        return false;
    }

    public void resetBindInfo(String currKey) {
        List<String> removeKeys = new ArrayList<>();
        for (Map.Entry<String, PageData> entry : pageDataHashMap.entrySet()) {
            if (!entry.getKey().equals(currKey)) {
                removeKeys.add(entry.getKey());
            }
        }
        for (String key : removeKeys) {
            pageDataHashMap.remove(key);
        }
    }

    /**
     * 加载数据
     *
     * @return
     */
    public Collection<Object> loadNextData(String key, Object parentData) {
        if (ExcelWriteHelper.VALUE_NAME_ROOT_DATA.equals(key)) {
            return this.convertData2Collection(writeContext.getData());
        }
        int hashCode = -1;
        if (parentData != null) {
            hashCode = System.identityHashCode(parentData);
        }

        DataLoadChannel dataLoadChannel = this.getDataLoadChannel(key);
        if (dataLoadChannel != null) {
            PageData pageData = this.getCurrPageData(String.valueOf(hashCode));
            Collection<Object> items = new ArrayList<>();

            while (items.size() < batchDataSize) {
                Object data = dataLoadChannel.loadData(pageData, this, parentData);
                this.callLoadListener(pageData, data, parentData);
                pageData.setCurrPage(pageData.getCurrPage() + 1);
                System.out.println("加载的数据" + data);
                if (LangUtils.isNotEmpty(data)) {
                    Collection<Object> list = this.convertData2Collection(data);
                    items.addAll(list);
                } else {
                    break;
                }
            }
            return items;
        }
        return this.convertData2Collection(writeContext.getData());
    }


    public void callLoadListener(PageData pageData, Object data, Object parentData) {
        List<DataLoadListener> dataLoadListeners = writeContext.getDataLoadListeners();
        if (LangUtils.isNotEmpty(dataLoadListeners)) {
            for (DataLoadListener listener : dataLoadListeners) {
                listener.load(pageData, data, parentData);
            }
        }
    }

    public Collection<Object> convertData2Collection(Object data) {
        Collection<Object> items = new ArrayList<Object>();
        if (data != null) {
            if (data instanceof Collection) {
                return (Collection) data;
            } else {
                items.add(data);
            }
            return items;
        }
        return items;
    }

    public String getOgnl(String str) {
        String val = onglMap.get(str);
        if (val == null) {
            String[] vals = this.getVaribles(str);
            if (vals != null && vals.length > 0) {
                val = vals[0];
                this.onglMap.put(str, val);
            }
        }
        return val;
    }

    public String[] getVaribles(String str) {
        if (str == null || str.equals("")) {
            return null;
        }
        int i1 = 0, i2 = 0, i = 0;
        String[] _retStr = new String[str.length() / 3];
        String[] retStr;
        while ((i1 = str.indexOf("${", i2)) != -1) {
            i2 = str.indexOf("}", i1);
            _retStr[i++] = str.substring(i1 + 2, i2);
        }
        retStr = new String[i];
        for (int j = 0; j < i; j++) {
            retStr[j] = _retStr[j];
        }
        return retStr;
    }


    public Row createNewRow(int writeRow) {
        Row row = writeContext.getActivitySheet().createRow(writeRow);
        /*if(this.rateSize>0&&writeRowNum%this.rateSize==0){
            AppProgress am=this.getAppProgress();
            if(am!=null){
                am.addComplete(this.getRateSize());
                am.flush("正在写入数据，已完成："+am.getComplete()+"，总数："+am.getTotal()+"，进度："+am.getRate()+"%");
            }

        }*/
        this.setWriteRowNum(writeRow + 1);
        return row;
    }

    public void writeRowCells(Row row, Object data, RowEntity rowEntity, int writeRow) {
        Row thisRow = rowEntity.getThisRow();
        row.setHeight(thisRow.getHeight());
        row.setRowStyle(thisRow.getRowStyle());

        int index = 0;
        int writeCellNum = 0;
        List<CellEntity> cellEntities = rowEntity.getCellEntities();
        //int columnNum=thisRow.getPhysicalNumberOfCells();
        int columnNum = cellEntities.size();
        for (int c = 0; c < cellEntities.size(); c++) {
            index++;
            CellEntity cellEntity = cellEntities.get(index - 1);
            TemplateTag templateTag = cellEntity.getTemplateTag();
            if (templateTag != null) {
                /*
                 * 单多列的数据遍历
                 */
                int start = templateTag.getTagStartIndex();
                int end = templateTag.getEndIndex();
                int ic = end - start + 1;
                List<CellEntity> cols = new ArrayList<CellEntity>();
                for (int j = start + 1; j < end; j++) {
                    CellEntity col = cellEntities.get(j);
                    col.setIndex(j);
                    cols.add(col);
                }
                Object itemVal = null;
                String ognl = templateTag.getValue();
                if (data != null) {
                    Collection<Object> collection = new ArrayList<Object>();
                    ognl = this.getOgnl(ognl);
                    itemVal = OgnlUtils.getValue(ognl, data);
                    if (itemVal != null) {
                        if (itemVal instanceof Object[] || itemVal instanceof Collection) {
                            collection = (Collection) itemVal;
                        } else {
                            collection.add(itemVal);
                        }
                    }

                    for (Object subItemsVal : collection) {
                        Map<String, Object> mapData = new HashMap<String, Object>();
                        mapData.put(templateTag.getVar(), subItemsVal);
                        for (CellEntity col : cols) {
                            int ix = col.getIndex();
                            Cell srcCell = thisRow.getCell(ix);
                            Cell distCell = row.createCell(writeCellNum++);
                            this.setCellValue(col, srcCell, distCell, mapData, writeRow, writeCellNum - 1);
                        }
                    }

                }
                c += ic;
            } else {
                Cell srcCell = thisRow.getCell(c);
                Cell distCell = row.createCell(writeCellNum++);
                this.setCellValue(cellEntity, srcCell, distCell, data, writeRow, c);
            }


        }
    }


    public void setCellValue(CellEntity cellEntity, Cell srcCell, Cell distCell, Object data, int writeRowNum, int writeCellNum) {

        if (srcCell == null) {
            return;
        }
        String cellVal = (srcCell.toString());
        if (cellVal == null) {
            return;
        }

        int[] colIndexs = cellEntity.getCosIndex();
        if (colIndexs != null) {
            int ci = writeCellNum;
            int startcell = ci;
            int startrow = writeRowNum;
            int merr = colIndexs[1] - colIndexs[0];
            int merg = colIndexs[3] - colIndexs[2];

            if (merg > 0 || merr > 0) {
                CellRangeAddress cra = new CellRangeAddress(startrow - merr, startrow, startcell, startcell + merg);
                activitySheet.addMergedRegion(cra);
            }

        }
        CellStyle cellStyle = srcCell.getCellStyle();
        distCell.setCellStyle(cellStyle);

        if (srcCell.getCellComment() != null) {
            distCell.setCellComment(srcCell.getCellComment());
        }

        String valStr = "";
        if (LangUtils.isEmpty(cellVal) && cellEntity.getColValue() != null) {
            cellVal = cellEntity.getColValue();
        }

        CellType srcCellType = srcCell.getCellType();
        Object value = this.getPropertyVal(data, cellVal);
        if (value == null) {
            distCell.setCellType(CellType.BLANK);
            return;
        }

        // 不同数据类型处理
        if (value != null) {
            valStr = String.valueOf(value);
            if (value instanceof Double) {
                distCell.setCellType(CellType.NUMERIC);
                srcCellType = CellType.NUMERIC;
            } else if (cellVal.contains("${") && valStr.endsWith("%")) {
                String v = valStr.replace("%", "");
                boolean f = MatcherUtils.matcherRegExp(MatcherUtils.NUMBER, v);
                if (f) {
                    distCell.setCellType(CellType.NUMERIC);
                    srcCellType = CellType.NUMERIC;
                }

            }
        }
        if (srcCellType == CellType.NUMERIC) {


            if (value != null) {
                if (value instanceof Double) {
                    Double val = (Double) value;
                    distCell.setCellValue(val);
                } else if (valStr.endsWith("%")) {
                    String v = valStr.replace("%", "");

                    double val = Double.parseDouble(v) / 100.0;
                    distCell.setCellValue(val);
                    CellStyle cs = workbook.createCellStyle();
                    this.copyCellStyle(cellStyle, cs);
                    cs.setDataFormat(HSSFDataFormat.getBuiltinFormat("0.00%"));
                    distCell.setCellStyle(cs);


                }
            }

        } else if (srcCellType == CellType.STRING) {
            String val = String.valueOf(value);
            if (value == null) {
                if (cellVal.contains("${")) {
                    distCell.setCellValue("");
                } else {
                    distCell.setCellValue(srcCell.getStringCellValue());
                }

            } else {
                distCell.setCellValue(val);
            }

        } else if (srcCellType == CellType.BLANK) {
            // TODO
        } else if (srcCellType == CellType.BOOLEAN) {
            distCell.setCellValue(srcCell.getBooleanCellValue());
        } else if (srcCellType == CellType.ERROR) {
            distCell.setCellErrorValue(srcCell.getErrorCellValue());
        } else if (srcCellType == CellType.FORMULA) {
            distCell.setCellFormula(srcCell.getCellFormula());
            this.bindCellFormula(distCell, writeRowNum, writeCellNum);
        }
    }

    public void bindCellFormula(Cell distCell, int writeRowNum, int writeCellNum) {
        String fm = distCell.getCellFormula();
        if (fm.split("5").length >= 2 && fm.endsWith("5")) {
            fm = fm.replace("5", writeRowNum + "");
            distCell.setCellFormula(fm);
        }

    }

    public void copyCellStyle(CellStyle fromStyle,
                              CellStyle toStyle) {
        toStyle.cloneStyleFrom(fromStyle);
    }

    public Object getPropertyVal(Object data, String key) {
        if (LangUtils.isNotEmpty(key) && LangUtils.isNotEmpty(data)) {
            if (key.startsWith("${")) {
                key = this.getOgnl(key);
                Map map = null;
                if (data instanceof Map) {
                    map = (Map) data;
                }
                String[] keys = key.split("\\.");
                if (keys.length == 2) {
                    String key1 = keys[0];
                    String key2 = keys[1];
                    Object it = map.get(key1);
                    return this.getMethodResVal(it, key2);

                } else if (keys.length == 1) {
                    if (map != null) {
                        return map.get(key);
                    } else {
                        return data;
                    }


                } else {
                    OgnlParser ognlParser = new OgnlParser(data);
                    return ognlParser.getValue(key);
                }
            }
        } else {
            return key;
        }
        return key;
    }

    public Object getMethodResVal(Object it, String propertyName) {
        String itClasName = it.getClass().getCanonicalName();
        if (it instanceof Map) {
            Map map = (Map) it;
            return map.get(propertyName);
        } else {
            try {
                String getterMethodName = "get" + StringUtils.capitalize(propertyName);
                String key = itClasName + "." + getterMethodName;
                Method method = methodMap.get(key);
                if (method == null) {
                    method = RefUtils.getAccessibleMethod(it, getterMethodName);
                    methodMap.put(key, method);
                }
                if (method != null) {
                    Object val = method.invoke(it);
                    return val;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        return null;
    }
}
